
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include "logger.h"
struct Postmark{
    char* fmsg;
    int offset;
};
char* buf;
int init_blocker=0;
int msg_queue;
volatile int head,tail;
int size;
const int block_size=0xf;
enum lvl LEVEL;
FILE* file;
int PREFFIX_MAX_LEN=20;
pthread_t flusher_thread;
pthread_mutex_t flush_mutex;
pthread_cond_t flush_cond;
//атомарный инкремент
inline int fetch_and_add( int * variable, int value ) {
    asm volatile("lock; xaddl %%eax, %2;"
                 :"=a" (value)                  //Output
                 :"a" (value), "m" (*variable)  //Input
                 :"memory");
    return value;
}
//процесс "почтальон"
void* postman(void * raw_postmark){
    struct Postmark* postmark=(struct Postmark*) raw_postmark;
     fprintf(stderr,"#%i#%s#\n",postmark->offset,postmark->fmsg);
    int msg_len=strlen(postmark->fmsg);
    pthread_mutex_lock(&flush_mutex);
    //check if msg_len<0
    int head_val=postmark->offset+msg_len-1;
    while(head_val>=head){
        pthread_cond_wait(&flush_cond,&flush_mutex);
    }
    int i;
    for(i=0;i<msg_len;i++){
        buf[((postmark->offset+i)%size)]=postmark->fmsg[i];
    }
    pthread_mutex_unlock(&flush_mutex);
    fetch_and_add(&msg_queue,-1);
    free(postmark->fmsg);
    pthread_exit(NULL);
}

void* flusher(void* noargs){
    //TODO catch signal from deinit, wait for all blocked postman go away
    int i;
    for(;;){
        pthread_mutex_lock(&flush_mutex);
        for(;buf[(head)%size];fetch_and_add(&head,1)){
            fputc(buf[(head)%size],file);
            buf[(head)%size]=0;
        }
        pthread_cond_signal(&flush_cond);
        pthread_mutex_unlock(&flush_mutex);
        usleep(1);
        if(((init_blocker==0)&&(msg_queue==0))&&(head-tail==size)){
        //If we should end and nobody waiting and whole buffer was flushed, we do it.

            pthread_exit(NULL);
        }
    }
}
int log_init(log_attr_t log_level, const char* log_file, int buffer){
    if(init_blocker==0){
        if(fetch_and_add(&init_blocker,1)==0){
            LEVEL=log_level;
            size=buffer;
            tail=0;
            head=size;
            buf=(char*)calloc(size,sizeof(char));
            msg_queue=0;
//!!!TODO catch errors
            file=fopen(log_file,"wt");
	//Блокировки "почтальонов"
            pthread_mutex_init(&flush_mutex,NULL);
            pthread_cond_init(&flush_cond,NULL);
	//Поток который опусташает буфер
            pthread_create(&flusher_thread,NULL,flusher,NULL);
        }
    }
    return 0;
}
void set_prefix(enum lvl level, char* str){
    switch(level){
    case INFO:
        strcpy(str,"INFO: ");
        return;
    case DEBUG:
        strcpy(str,"DEBUG: ");
        return;
    case WARNING:
        strcpy(str,"WARNING: ");
        return;
    case ERROR:
        strcpy(str,"ERROR: ");
        return;
    }
}
int sendMsg(enum lvl msg_lvl,const char* msg){
    if(init_blocker==0){
        return -1;
    }
    if((msg_lvl&LEVEL) == 0){
        return 0;
    }
    fetch_and_add(&msg_queue,1);
    int msg_len=strlen(msg);
    char* fmsg=(char*)malloc(msg_len*sizeof(char)+PREFFIX_MAX_LEN);

    if(fmsg==NULL){
	exit(-1);
    }
    set_prefix(msg_lvl,fmsg);
    strcat(fmsg,msg);
    msg_len=strlen(fmsg);

    int offset=fetch_and_add(&tail,msg_len);
    if(offset+msg_len<head){
	//в буфере хватит места на сообщение - пишем сами
        int i;
        for(i=0;i<msg_len;i++){
            *(buf+((offset+i)%size))=*(fmsg+i);
        }
        fetch_and_add(&msg_queue,-1);
        free(fmsg);
    }else{
	//не хватит - отдаём "почтальону" (потоку который будет ждать освобождения буфера)
        struct Postmark postmark;
        postmark.fmsg=fmsg;
        postmark.offset=offset;
        pthread_t postman_thread;
        pthread_create(&postman_thread, NULL, postman, (void*)&postmark);
    }
    return 0;
}

int log_deinit(){
    if(init_blocker==0){
        return -1;
    }
    init_blocker=0;
    pthread_join(flusher_thread,NULL);
    pthread_mutex_destroy(&flush_mutex);
    pthread_cond_destroy(&flush_cond);
    fclose(file);
    return 0;
}

int sendErr(const char* msg){
    return sendMsg(ERROR,msg);
}
int sendWrn(const char* msg){
    return sendMsg(WARNING,msg);
}
int sendInf(const char* msg){
    return sendMsg(INFO,msg);
}
int sendDbg(const char* msg){
    return sendMsg(DEBUG,msg);
}
